/* 
 * File:   main.c
 * Author: marco
 *
 * Created on 8 de Abril de 2014, 17:20
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*
 * Declaração de Constantes
 */
#define REG_TAM_MAX 256
#define NUM_MAX_REG 500
#define NUM_APROVADOS 15
#define TAM_MAX_INDICE_SEC 350
#define TAM_CHAVE_PRI 6
#define TAM_MAX_CHAVE_SEC 50
#define TAM_NOME 50
#define TAM_CURSO 30
#define TAM_NOTA 6
#define DELIMITADOR_CHAR '|'
#define DELIMITADOR_STRING "|"
#define FLAG_REG_REMOVIDO '*'

/*
 * Declaração de Tipos
 */
typedef struct {
    char chave[TAM_CHAVE_PRI + 1]; //PARA O \0
    unsigned int offset;
} PrimaryIndexElement;

struct PrimaryIndex {
    PrimaryIndexElement vetor[NUM_MAX_REG];
    unsigned int tamanho;
};

typedef struct {
    char chave[TAM_MAX_CHAVE_SEC + 1]; //PARA O \0
    int rrnProx;
} SecundaryIndexElement;

struct SecundaryIndex {
    SecundaryIndexElement vetor[TAM_MAX_INDICE_SEC];
    unsigned int tamanho;
};

typedef struct {
    char chave[TAM_CHAVE_PRI + 1]; //PARA O \0
    int rrnProxNome;
    int rrnProxCurso;
} InvertedIndexElement;

struct InvertedIndex {
    InvertedIndexElement vetor[NUM_MAX_REG];
    unsigned int tamanho;
};

struct Registro {
    char inscricao[TAM_CHAVE_PRI + 1]; //PARA O \0
    char nome[TAM_NOME + 1];
    char curso[TAM_CURSO + 1];
    char nota[TAM_NOTA + 1];
};
int comparaChaves(const void* chave1, const void* chave2);
int comparaNotas(const void* reg1, const void* reg2);
void criaIndicePrimario(FILE* fRegistros, struct PrimaryIndex* index);
void criaListaInvertida(FILE* fRegistros, struct PrimaryIndex* index, struct SecundaryIndex* nomes, struct SecundaryIndex* cursos, struct InvertedIndex* listaInvertida);
int insereIndicePrimario(struct PrimaryIndex* indice, char chave[TAM_CHAVE_PRI + 1], int offset);
int insereIndiceSecundario(struct SecundaryIndex* indice, char* chave);
void insereListaInvertida(struct InvertedIndex* listaInvertida, SecundaryIndexElement* nome, SecundaryIndexElement* curso, char* chave);
int insereAluno(FILE* fRegistros, struct PrimaryIndex* index, struct SecundaryIndex* nomes, struct SecundaryIndex* cursos, struct InvertedIndex* listaInvertida);
int removeAluno(FILE* fRegistros, struct PrimaryIndex* index, struct SecundaryIndex* nomes, struct SecundaryIndex* cursos, struct InvertedIndex* listaInvertida);
void buscaBinaria(void* vetor, size_t tamanhoElemento, size_t tamanho, char* chave, int* achou, int* pos);
int buscaIndicePrimario(char* chave, struct PrimaryIndex* index);
int buscaIndiceSecundario(char* chave, struct SecundaryIndex* index);
void percorreNomes(char* chave, int rrnInicio, struct InvertedIndex* listaInvertida, int* rrnAnt, int* rrn);
void percorreCursos(char* chave, int rrnInicio, struct InvertedIndex* listaInvertida, int* rrnAnt, int* rrn);
void printListaInvertida(struct InvertedIndex* listaInvertida);
void printIndiceSecundario(struct SecundaryIndex* sec, char* nome);
void printIndicePrimario(struct PrimaryIndex* index);
void printIndices(struct PrimaryIndex* index, struct SecundaryIndex* nomes, struct SecundaryIndex* cursos, struct InvertedIndex* listaInvertida);
void printRegistro(struct Registro* registro);
void importaIndices(FILE* fRegistros, FILE* fIndex, struct PrimaryIndex * index, FILE* fNomes, struct SecundaryIndex * nomes, FILE* fCursos, struct SecundaryIndex * cursos, FILE* fListaInvertida, struct InvertedIndex* listaInvertida);
void persisteIndices(FILE* fIndex, struct PrimaryIndex * index, FILE* fNomes, struct SecundaryIndex * nomes, FILE* fCursos, struct SecundaryIndex * cursos, FILE* fListaInvertida, struct InvertedIndex* listaInvertida);
void importaDados(FILE* fRegistros);
void menu(char* escolhas);
int getCampo(char * buffer, char* campo);
void getRegistro(struct Registro* registro, long int offset, FILE* fRegistros);
void atualizaFlags(FILE* fIndex, FILE* fNomes, FILE* fCursos, FILE* fListaInvertida);
void compactaArquivoRegistros(FILE* fRegistros);

/*
 * Programa para Gerenciar Arquivos com Índices Lineares
 */
int main(int argc, char** argv) {

    printf(" ********** Programa Gerenciador de Arquivos **********\n\n");

    FILE* fRegistros;
    FILE* fIndex;
    FILE* fNomes;
    FILE* fCursos;
    FILE* fListaInvertida;

    char* opc = malloc(2);

    struct PrimaryIndex index;
    index.tamanho = 0;
    struct SecundaryIndex nomes;
    nomes.tamanho = 0;
    struct SecundaryIndex cursos;
    cursos.tamanho = 0;
    struct InvertedIndex listaInvertida;
    listaInvertida.tamanho = 0;
    int flagsAtualizadas = 0;
    if ((fRegistros = fopen("registros.dat", "r+")) == NULL) {
        opc[0] = '1';
        fRegistros = fopen("registros.dat", "w+");
    } else {
        importaIndices(fRegistros, fIndex, &index, fNomes, &nomes, fCursos, &cursos, fListaInvertida, &listaInvertida);
        flagsAtualizadas = 1;
        menu(opc);
    }
    while (opc[0] != '0') {
        char chave[TAM_CHAVE_PRI + 1]; //PARA O \0
        int rrn;
        struct Registro registro;
        int rrnInd;
        int erro;
        switch (opc[0]) {
            case '1':
                importaDados(fRegistros);
                criaIndicePrimario(fRegistros, &index);
                criaListaInvertida(fRegistros, &index, &nomes, &cursos, &listaInvertida);
                persisteIndices(fIndex, &index, fNomes, &nomes, fCursos, &cursos, fListaInvertida, &listaInvertida);
                flagsAtualizadas = 1;
                printf("Importação efetuada com sucesso!\n");
                break;
            case '2':
                do {
                    erro = insereAluno(fRegistros, &index, &nomes, &cursos, &listaInvertida);
                } while (erro == 1);
                if (flagsAtualizadas) {
                    atualizaFlags(fIndex, fNomes, fCursos, fListaInvertida);
                    flagsAtualizadas = 0;
                }
                if (erro == 2) {
                    printf("Erro: Número máximo de registros atingido!\n");
                }
                break;
            case '3':
                if (removeAluno(fRegistros, &index, &nomes, &cursos, &listaInvertida) && flagsAtualizadas) {
                    atualizaFlags(fIndex, fNomes, fCursos, fListaInvertida);
                    flagsAtualizadas = 0;
                }
                break;
            case '4':
                switch (opc[1]) {
                    case '1':
                        printf("Informe o nº de inscrição: ");
                        gets(chave);
                        rrnInd = buscaIndicePrimario(chave, &index);
                        if (rrnInd == -1)
                            printf("Registro %s não encontrado!\n", chave);
                        else {
                            getRegistro(&registro, index.vetor[rrnInd].offset, fRegistros);
                            printRegistro(&registro);
                        }
                        break;
                    case '2':
                        printf("Informe o nome: ");
                        gets(chave);
                        rrnInd = buscaIndiceSecundario(chave, &nomes);
                        if (rrnInd == -1 || (rrn = nomes.vetor[rrnInd].rrnProx) == -1)
                            printf("Nome %s não encontrado!\n", chave);
                        else {
                            while (rrn != -1) {
                                rrnInd = buscaIndicePrimario(listaInvertida.vetor[rrn].chave, &index);
                                getRegistro(&registro, index.vetor[rrnInd].offset, fRegistros);
                                printRegistro(&registro);
                                rrn = listaInvertida.vetor[rrn].rrnProxNome;
                            }
                        }
                        break;
                    case '3':
                        printf("Informe o curso: ");
                        gets(chave);
                        rrnInd = buscaIndiceSecundario(chave, &cursos);
                        if (rrnInd == -1 || (rrn = cursos.vetor[rrnInd].rrnProx) == -1)
                            printf("Curso %s não encontrado!\n", chave);
                        else {
                            while (rrn != -1) {
                                rrnInd = buscaIndicePrimario(listaInvertida.vetor[rrn].chave, &index);
                                getRegistro(&registro, index.vetor[rrnInd].offset, fRegistros);
                                printRegistro(&registro);
                                rrn = listaInvertida.vetor[rrn].rrnProxCurso;
                            }
                        }
                        break;
                    case '4':
                        printf("Informe o curso: ");
                        gets(chave);
                        rrnInd = buscaIndiceSecundario(chave, &cursos);
                        if (rrnInd == -1 || (rrn = cursos.vetor[rrnInd].rrnProx) == -1)
                            printf("Curso %s não encontrado!\n", chave);
                        else {
                            struct Registro registros[TAM_MAX_INDICE_SEC];
                            int n = 0;
                            rrn = cursos.vetor[rrnInd].rrnProx;
                            while (rrn != -1) {
                                rrnInd = buscaIndicePrimario(listaInvertida.vetor[rrn].chave, &index);
                                getRegistro(&registro, index.vetor[rrnInd].offset, fRegistros);
                                registros[n++] = registro;
                                rrn = listaInvertida.vetor[rrn].rrnProxCurso;
                            }
                            qsort(registros, n, sizeof (struct Registro), comparaNotas);
                            printf("****    Lista de aprovados:    ****\n\n");
                            int i = n - 1;
                            int j = 1;
                            for (; i >= 0 && i >= n - NUM_APROVADOS; i--) {
                                printf("Colocação: %2dº\n", j++);
                                printRegistro(&registros[i]);
                            }
                        }
                        break;
                }
                break;
            case '5':
                compactaArquivoRegistros(fRegistros);
                criaIndicePrimario(fRegistros, &index);
                criaListaInvertida(fRegistros, &index, &nomes, &cursos, &listaInvertida);
                persisteIndices(fIndex, &index, fNomes, &nomes, fCursos, &cursos, fListaInvertida, &listaInvertida);
                printf("Compactação realizada com sucesso!\n");
                break;
            case '6':
                printIndices(&index, &nomes, &cursos, &listaInvertida);
                break;
        }
        menu(opc);
    }

    fclose(fRegistros);

    if (!flagsAtualizadas)
        persisteIndices(fIndex, &index, fNomes, &nomes, fCursos, &cursos, fListaInvertida, &listaInvertida);

    exit(EXIT_SUCCESS);
}

void menu(char* escolhas) {
    char opcao[2];
    do {
        printf("Menu de Opções:\n");
        printf("<0> - Sair\n");
        printf("<1> - Importação de dados\n");
        printf("<2> - Inserção de um novo aluno\n");
        printf("<3> - Remoção de um aluno\n");
        printf("<4> - Buscas e Listagens\n");
        printf("<5> - Compactação do arquivo de registros\n");
        printf("<6> - Imprimir todos os índices\n");
        printf("\nOpção: ");
        gets(opcao);
    } while (opcao[0] < '0' || opcao[0] > '6');
    escolhas[0] = opcao[0];
    if (opcao[0] == '4') {
        do {
            do {
                printf("*** Buscas e Listagens ***\n\n");
                printf("<0> - Voltar\n");
                printf("<1> - Buscar por Nº de Inscrição:\n");
                printf("<2> - Buscar por Nome\n");
                printf("<3> - Buscar por Curso\n");
                printf("<4> - Listar aprovados\n");
                printf("\nOpção: ");
                gets(opcao);
            } while (opcao[0] < '0' || opcao[0] > '4');
            escolhas[1] = opcao[0];
        } while (escolhas[1] == '4' && escolhas[2] == '0');
    }
}

void importaIndices(FILE* fRegistros, FILE* fIndex, struct PrimaryIndex * index, FILE* fNomes, struct SecundaryIndex * nomes, FILE* fCursos, struct SecundaryIndex * cursos, FILE* fListaInvertida, struct InvertedIndex* listaInvertida) {
    if ((fIndex = fopen("index.dat", "r+")) == NULL) {
        printf("Erro: Não foi possível abrir o arquivo de índice primário \"index.dat\"!");
        exit(EXIT_FAILURE);
    }
    if ((fNomes = fopen("nomes.dat", "r+")) == NULL) {
        printf("Erro: Não foi possível abrir o arquivo de índice secundário \"nomes.dat\"!");
        exit(EXIT_FAILURE);
    }
    if ((fCursos = fopen("cursos.dat", "r+")) == NULL) {
        printf("Erro: Não foi possível abrir o arquivo de índice secundário \"cursos.dat\"!");
        exit(EXIT_FAILURE);
    }
    if ((fListaInvertida = fopen("listaInvertida.dat", "r+")) == NULL) {
        printf("Erro: Não foi possível abrir o arquivo de lista invertida \"listaInvertida.dat\"!");
        exit(EXIT_FAILURE);
    }

    char flags[3];
    fread(&flags[0], sizeof (char), 1, fIndex);

    /* 
     * Se flag "ativa" o arquivo "index.dat" está desatualizado em disco,
     * portanto recria todos os índices. 
     */
    if (flags[0] == '1') {
        criaIndicePrimario(fRegistros, index);
        criaListaInvertida(fRegistros, index, nomes, cursos, listaInvertida);
        return;
    }

    fread(&flags[0], sizeof (char), 1, fNomes);
    fread(&flags[1], sizeof (char), 1, fCursos);
    fread(&flags[2], sizeof (char), 1, fListaInvertida);

    /* 
     * Se qualquer um dos arquivos está desatualizado em disco, 
     * recria a lista invertida
     */
    if (flags[0] == '1' || flags[1] == '1' || flags[2] == '1') {
        criaListaInvertida(fRegistros, index, nomes, cursos, listaInvertida);
        return;
    }

    fread(&index->tamanho, sizeof (index->tamanho), 1, fIndex);
    fread(index->vetor, sizeof (PrimaryIndexElement), index->tamanho, fIndex);
    fread(&nomes->tamanho, sizeof (nomes->tamanho), 1, fNomes);
    fread(nomes->vetor, sizeof (SecundaryIndexElement), nomes->tamanho, fNomes);
    fread(&cursos->tamanho, sizeof (cursos->tamanho), 1, fCursos);
    fread(cursos->vetor, sizeof (SecundaryIndexElement), cursos->tamanho, fCursos);
    fread(&listaInvertida->tamanho, sizeof (listaInvertida->tamanho), 1, fListaInvertida);
    fread(listaInvertida->vetor, sizeof (InvertedIndexElement), listaInvertida->tamanho, fListaInvertida);
}

void importaDados(FILE* fRegistros) {
    FILE* fDados;
    char filename[30];
    do {
        printf("Informe o nome o arquivo de dados de entrada:\n>> ");
        gets(filename);

        if ((fDados = fopen(filename, "r")) == NULL) {
            printf("\n\nErro: Não foi possível abrir o arquivo de dados!\n"
                    "Certifique-se que o nome do arquivo está correto e\n"
                    "na mesma pasta do programa\n"
                    "Para sair: Ctrl + C\n\n");
        }
    } while (fDados == NULL);

    //escreve cabeçalho
    int qtdregs = 0;
    fwrite(&qtdregs, 1, sizeof (qtdregs), fRegistros);

    char c;
    char* buffer = malloc(REG_TAM_MAX);
    int i = 0;
    while ((c = fgetc(fDados)) != EOF) {
        if (c == '\t') {
            buffer[i++] = DELIMITADOR_CHAR;
        } else if (c == '\n') {
            buffer[i++] = DELIMITADOR_CHAR;
            fwrite(&i, 1, sizeof (i), fRegistros);
            fwrite(buffer, i, 1, fRegistros);
            qtdregs++;
            i = 0;
        } else if (c != '\r') {
            buffer[i++] = c;
        }
    }
    free(buffer);
    rewind(fRegistros);
    // atualiza cabeçalho
    fwrite(&qtdregs, 1, sizeof (qtdregs), fRegistros);
}

void criaIndicePrimario(FILE* fRegistros, struct PrimaryIndex* index) {

    void getChave(char* reg, char* chave) {
        int i;
        for (i = 0; i < TAM_CHAVE_PRI; i++) {
            chave[i] = reg[i];
        }
        chave[i] = '\0';
    }

    index->tamanho = 0;
    char* buffer = (char *) malloc(REG_TAM_MAX);
    int regTam;
    char chave[TAM_CHAVE_PRI + 1]; //para o \0
    int offset;
    int i;
    int n;
    rewind(fRegistros);
    fread(&n, sizeof (n), 1, fRegistros);
    for (i = 0; i < n; i++) {
        offset = ftell(fRegistros);
        fread(&regTam, sizeof (regTam), 1, fRegistros);
        fread(buffer, sizeof (char), regTam, fRegistros);
        getChave(buffer, chave);
        insereIndicePrimario(index, chave, offset);
    }
    free(buffer);
}

int insereIndicePrimario(struct PrimaryIndex* indice, char chave[TAM_CHAVE_PRI + 1], int offset) {
    sprintf(chave, "%*d", TAM_CHAVE_PRI, atoi(chave));
    int rrn;
    int achou;
    buscaBinaria(indice->vetor, sizeof (indice->vetor[0]), indice->tamanho, chave, &achou, &rrn);
    if (achou) {
        printf("Erro: Chave %s já existe!\n", chave);
        return 1;
    } else {
        int posInserir = rrn;
        if (posInserir >= NUM_MAX_REG)
            return 2;
        if (indice->tamanho > 0) {
            if (strcmp(chave, indice->vetor[rrn].chave) > 0)
                posInserir = rrn + 1;
            int i = indice->tamanho - 1;
            for (; i >= posInserir; i--) {
                strcpy(indice->vetor[i + 1].chave, indice->vetor[i].chave);
                indice->vetor[i + 1].offset = indice->vetor[i].offset;
            }
        }
        strcpy(indice->vetor[posInserir].chave, chave);
        indice->vetor[posInserir].offset = offset;
        indice->tamanho++;
        return 0;
    }
}

int comparaChaves(const void* chave1, const void* chave2) {
    return strcmp((char*) chave1, (char*) chave2);
}

int comparaNotas(const void* reg1, const void* reg2) {
    float nota1 = atof(((struct Registro*) reg1)->nota);
    float nota2 = atof(((struct Registro*) reg2)->nota);
    if (nota1 > nota2)
        return 1;
    else if (nota1 < nota2)
        return -1;
    else
        return 0;
}

void criaListaInvertida(FILE* fRegistros, struct PrimaryIndex* index, struct SecundaryIndex* nomes, struct SecundaryIndex* cursos, struct InvertedIndex* listaInvertida) {

    nomes->tamanho = 0;
    cursos->tamanho = 0;
    listaInvertida->tamanho = 0;
    rewind(fRegistros);
    int n;
    fread(&n, sizeof (n), 1, fRegistros);
    int i = 0;
    struct Registro registro;
    int rrnNomeInserido;
    int rrnCursoInserido;
    for (; i < n; i++) {
        getRegistro(&registro, ftell(fRegistros), fRegistros);
        rrnNomeInserido = insereIndiceSecundario(nomes, registro.nome);
        rrnCursoInserido = insereIndiceSecundario(cursos, registro.curso);
        insereListaInvertida(listaInvertida, &nomes->vetor[rrnNomeInserido], &cursos->vetor[rrnCursoInserido], registro.inscricao);
    }
}

int insereIndiceSecundario(struct SecundaryIndex* indice, char* chave) {

    int rrn;
    int achou;
    buscaBinaria(indice->vetor, sizeof (indice->vetor[0]), indice->tamanho, chave, &achou, &rrn);
    if (!achou) {
        int posInserir = rrn;
        if (indice->tamanho > 0) {
            if (strcmp(chave, indice->vetor[rrn].chave) > 0)
                posInserir = rrn + 1;
            int i = indice->tamanho - 1;
            for (; i >= posInserir; i--) {
                strcpy(indice->vetor[i + 1].chave, indice->vetor[i].chave);
                indice->vetor[i + 1].rrnProx = indice->vetor[i].rrnProx;
            }
        }
        strcpy(indice->vetor[posInserir].chave, chave);
        indice->vetor[posInserir].rrnProx = -1;
        indice->tamanho++;
        rrn = posInserir;
    }
    return rrn;
}

void insereListaInvertida(struct InvertedIndex* listaInvertida, SecundaryIndexElement* nome, SecundaryIndexElement* curso, char* chave) {

    int rrnInserido = listaInvertida->tamanho++;
    strcpy(listaInvertida->vetor[rrnInserido].chave, chave);
    int rrn;
    int rrnAnt;
    percorreNomes(chave, nome->rrnProx, listaInvertida, &rrnAnt, &rrn);
    if (rrnAnt == -1) {
        listaInvertida->vetor[rrnInserido].rrnProxNome = nome->rrnProx;
        nome->rrnProx = rrnInserido;
    } else {
        listaInvertida->vetor[rrnInserido].rrnProxNome = listaInvertida->vetor[rrnAnt].rrnProxNome;
        listaInvertida->vetor[rrnAnt].rrnProxNome = rrnInserido;
    }
    percorreCursos(chave, curso->rrnProx, listaInvertida, &rrnAnt, &rrn);
    if (rrnAnt == -1) {
        listaInvertida->vetor[rrnInserido].rrnProxCurso = curso->rrnProx;
        curso->rrnProx = rrnInserido;
    } else {
        listaInvertida->vetor[rrnInserido].rrnProxCurso = listaInvertida->vetor[rrnAnt].rrnProxCurso;
        listaInvertida->vetor[rrnAnt].rrnProxCurso = rrnInserido;
    }
}

void percorreNomes(char* chave, int rrnInicio, struct InvertedIndex* listaInvertida, int* rrnAnt, int* rrn) {
    *rrn = rrnInicio;
    *rrnAnt = -1;
    while (*rrn != -1 && strcmp(chave, listaInvertida->vetor[*rrn].chave) > 0) {
        *rrnAnt = *rrn;
        *rrn = listaInvertida->vetor[*rrn].rrnProxNome;
    }
}

void percorreCursos(char* chave, int rrnInicio, struct InvertedIndex* listaInvertida, int* rrnAnt, int* rrn) {
    *rrn = rrnInicio;
    *rrnAnt = -1;
    while (*rrn != -1 && strcmp(chave, listaInvertida->vetor[*rrn].chave) > 0) {
        *rrnAnt = *rrn;
        *rrn = listaInvertida->vetor[*rrn].rrnProxCurso;
    }
}

void persisteIndices(FILE* fIndex, struct PrimaryIndex * index, FILE* fNomes, struct SecundaryIndex * nomes, FILE* fCursos, struct SecundaryIndex * cursos, FILE* fListaInvertida, struct InvertedIndex* listaInvertida) {
    fIndex = fopen("index.dat", "w+");
    fNomes = fopen("nomes.dat", "w+");
    fCursos = fopen("cursos.dat", "w+");
    fListaInvertida = fopen("listaInvertida.dat", "w+");

    char flag = '0';
    fwrite(&flag, sizeof (flag), 1, fIndex);
    fwrite(&index->tamanho, sizeof (index->tamanho), 1, fIndex);
    fwrite(index->vetor, sizeof (PrimaryIndexElement), index->tamanho, fIndex);
    fclose(fIndex);

    fwrite(&flag, sizeof (flag), 1, fNomes);
    fwrite(&nomes->tamanho, sizeof (nomes->tamanho), 1, fNomes);
    fwrite(nomes->vetor, sizeof (SecundaryIndexElement), nomes->tamanho, fNomes);
    fclose(fNomes);

    fwrite(&flag, sizeof (flag), 1, fCursos);
    fwrite(&cursos->tamanho, sizeof (cursos->tamanho), 1, fCursos);
    fwrite(cursos->vetor, sizeof (SecundaryIndexElement), cursos->tamanho, fCursos);
    fclose(fCursos);

    fwrite(&flag, sizeof (flag), 1, fListaInvertida);
    fwrite(&listaInvertida->tamanho, sizeof (listaInvertida->tamanho), 1, fListaInvertida);
    fwrite(listaInvertida->vetor, sizeof (InvertedIndexElement), listaInvertida->tamanho, fListaInvertida);
    fclose(fListaInvertida);
}

int insereAluno(FILE* fRegistros, struct PrimaryIndex* index, struct SecundaryIndex* nomes, struct SecundaryIndex* cursos, struct InvertedIndex* listaInvertida) {
    struct Registro registro;
    char* buffer = malloc(REG_TAM_MAX);
    printf("Informe Nº de Inscrição: ");
    gets(registro.inscricao);
    fseek(fRegistros, 0L, SEEK_END);
    int erro = insereIndicePrimario(index, registro.inscricao, ftell(fRegistros));
    if (!erro) {
        strcpy(buffer, registro.inscricao);
        strcat(buffer, DELIMITADOR_STRING);
        printf("Informe Nome: ");
        gets(registro.nome);
        printf("Informe Curso: ");
        gets(registro.curso);
        printf("Informe Nota: ");
        gets(registro.nota);
        strcat(buffer, registro.nome);
        strcat(buffer, DELIMITADOR_STRING);
        strcat(buffer, registro.curso);
        strcat(buffer, DELIMITADOR_STRING);
        strcat(buffer, registro.nota);
        strcat(buffer, DELIMITADOR_STRING);
        int tamReg = strlen(buffer);
        fwrite(&tamReg, sizeof (tamReg), 1, fRegistros);
        fwrite(buffer, sizeof (char), tamReg, fRegistros);
        rewind(fRegistros);
        fread(&tamReg, sizeof (tamReg), 1, fRegistros);
        rewind(fRegistros);
        tamReg++;
        fwrite(&tamReg, sizeof (tamReg), 1, fRegistros);
        fflush(fRegistros);
        int rrnNomeInserido = insereIndiceSecundario(nomes, registro.nome);
        int rrnCursoInserido = insereIndiceSecundario(cursos, registro.curso);
        insereListaInvertida(listaInvertida, &nomes->vetor[rrnNomeInserido], &cursos->vetor[rrnCursoInserido], registro.inscricao);
        printf("Registro %s inserido com sucesso!\n", registro.inscricao);
    }
    free(buffer);
    return erro;
}

int removeAluno(FILE* fRegistros, struct PrimaryIndex* index, struct SecundaryIndex* nomes, struct SecundaryIndex* cursos, struct InvertedIndex* listaInvertida) {
    char chave[TAM_CHAVE_PRI + 1];
    printf("Infome o nº de inscrição que deseja remover: ");
    gets(chave);
    int rrn;
    if ((rrn = buscaIndicePrimario(chave, index)) == -1)
        printf("Erro: Nº de inscrição %s não encontrado.\n", chave);
    else {
        struct Registro registro;
        long int offset = index->vetor[rrn].offset;
        getRegistro(&registro, offset, fRegistros);
        fseek(fRegistros, offset + sizeof (int), SEEK_SET);
        char c = FLAG_REG_REMOVIDO;
        fwrite(&c, sizeof (char), 1, fRegistros);
        int i = rrn;
        for (; i < index->tamanho - 1; i++) {
            index->vetor[i] = index->vetor[i + 1];
        }
        index->tamanho--;
        int rrnIndSec = buscaIndiceSecundario(registro.nome, nomes);
        int rrnAnt;
        percorreNomes(registro.inscricao, nomes->vetor[rrnIndSec].rrnProx, listaInvertida, &rrnAnt, &rrn);
        if (rrnAnt == -1) {
            if (rrn != -1) {
                nomes->vetor[rrnIndSec].rrnProx = listaInvertida->vetor[rrn].rrnProxNome;
                listaInvertida->vetor[rrn].rrnProxNome = -2;
            }
        } else {
            listaInvertida->vetor[rrnAnt].rrnProxNome = listaInvertida->vetor[rrn].rrnProxNome;
            listaInvertida->vetor[rrn].rrnProxNome = -2;
        }
        rrnIndSec = buscaIndiceSecundario(registro.curso, cursos);
        rrnAnt;
        percorreCursos(registro.inscricao, cursos->vetor[rrnIndSec].rrnProx, listaInvertida, &rrnAnt, &rrn);
        if (rrnAnt == -1) {
            if (rrn != -1) {
                cursos->vetor[rrnIndSec].rrnProx = listaInvertida->vetor[rrn].rrnProxCurso;
                listaInvertida->vetor[rrn].rrnProxCurso = -2;
            }
        } else {
            listaInvertida->vetor[rrnAnt].rrnProxCurso = listaInvertida->vetor[rrn].rrnProxCurso;
            listaInvertida->vetor[rrn].rrnProxCurso = -2;
        }
        printf("Registro %s removido com sucesso!\n", registro.inscricao);
    }
}

void compactaArquivoRegistros(FILE* fRegistros) {
    FILE* fNewRegistros;
    if ((fNewRegistros = fopen("registros.tmp", "w+")) == NULL) {
        printf("Erro: Não foi possível criar o arquivo \"registros.tmp\".");
        exit(EXIT_FAILURE);
    } else {
        rewind(fRegistros);
        fseek(fNewRegistros, sizeof (int), SEEK_SET);
        int n;
        int tamReg;
        char* buffer = (char*) malloc(REG_TAM_MAX);
        fread(&n, sizeof (n), 1, fRegistros);
        int newN = 0;
        int i = 0;
        for (; i < n; i++) {
            fread(&tamReg, sizeof (tamReg), 1, fRegistros);
            fread(buffer, sizeof (char), tamReg, fRegistros);
            if (buffer[0] != '*') {
                fwrite(&tamReg, sizeof (tamReg), 1, fNewRegistros);
                fwrite(buffer, sizeof (char), tamReg, fNewRegistros);
                newN++;
            }
        }
        fflush(fNewRegistros);
        fclose(fRegistros);
        if (remove("registros.dat") == -1) {
            printf("Erro: Não foi possível remover o arquivo \"registros.dat\".");
            exit(EXIT_FAILURE);
        }
        rewind(fNewRegistros);
        fwrite(&newN, sizeof (newN), 1, fNewRegistros);
        fclose(fNewRegistros);
        rename("registros.tmp", "registros.dat");
        if ((fRegistros = fopen("registros.dat", "r+")) == NULL) {
            printf("Erro: Não foi possível abrir o arquivo \"registros.dat\".");
            exit(EXIT_FAILURE);
        }
        free(buffer);
    }
}

void atualizaFlags(FILE* fIndex, FILE* fNomes, FILE* fCursos, FILE* fListaInvertida) {
    if ((fIndex = fopen("index.dat", "r+")) == NULL) {
        printf("Erro: Não foi possível abrir o arquivo de índice primário \"index.dat\"!");
        exit(EXIT_FAILURE);
    }
    if ((fNomes = fopen("nomes.dat", "r+")) == NULL) {
        printf("Erro: Não foi possível abrir o arquivo de índice secundário \"nomes.dat\"!");
        exit(EXIT_FAILURE);
    }
    if ((fCursos = fopen("cursos.dat", "r+")) == NULL) {
        printf("Erro: Não foi possível abrir o arquivo de índice secundário \"cursos.dat\"!");
        exit(EXIT_FAILURE);
    }
    if ((fListaInvertida = fopen("listaInvertida.dat", "r+")) == NULL) {
        printf("Erro: Não foi possível abrir o arquivo de lista invertida \"listaInvertida.dat\"!");
        exit(EXIT_FAILURE);
    }
    char flag = '1';
    fwrite(&flag, sizeof (flag), 1, fIndex);
    fwrite(&flag, sizeof (flag), 1, fNomes);
    fwrite(&flag, sizeof (flag), 1, fCursos);
    fwrite(&flag, sizeof (flag), 1, fListaInvertida);
    fclose(fIndex);
    fclose(fNomes);
    fclose(fCursos);
    fclose(fListaInvertida);
}

void printIndices(struct PrimaryIndex* index, struct SecundaryIndex* nomes, struct SecundaryIndex* cursos, struct InvertedIndex* listaInvertida) {
    printIndicePrimario(index);
    printIndiceSecundario(nomes, "Nomes");
    printIndiceSecundario(cursos, "Cursos");
    printListaInvertida(listaInvertida);
}

void printIndicePrimario(struct PrimaryIndex* index) {
    printf("\n*****   Índice Primário  *******\n");
    int i = 0;
    for (; i < index->tamanho; i++)
        printf("RRN:%3d | Chave: %s | offset: %6d\n", i, index->vetor[i].chave, index->vetor[i].offset);
}

void printIndiceSecundario(struct SecundaryIndex* sec, char* nome) {
    printf("\n*****   Índice Secundário %s  *******\n", nome);
    int i = 0;
    for (; i < sec->tamanho; i++)
        printf("RRN:%3d | Chave: %-50s | rrn: %3d\n", i, sec->vetor[i].chave, sec->vetor[i].rrnProx);
}

void printListaInvertida(struct InvertedIndex* listaInvertida) {
    printf("\n*****   Lista Invertida  *******\n");
    int i = 0;
    for (; i < listaInvertida->tamanho; i++)
        printf("RRN:%3d | Chave: %s | rrnProxNome: %3d | rrnProxCurso: %3d\n", i, listaInvertida->vetor[i].chave, listaInvertida->vetor[i].rrnProxNome, listaInvertida->vetor[i].rrnProxCurso);
}

void printRegistro(struct Registro* registro) {
    printf("\nRegistro:\n");
    printf("Nº de Inscrição: %s\n", registro->inscricao);
    printf("Nome: %s\n", registro->nome);
    printf("Curso: %s\n", registro->curso);
    printf("Nota: %s\n\n", registro->nota);
}

int getCampo(char * buffer, char* campo) {
    int i = 0;
    for (; buffer[i] != DELIMITADOR_CHAR; i++) {
        campo[i] = buffer[i];
    }
    campo[i++] = '\0';
    return i;
}

void getRegistro(struct Registro* registro, long int offset, FILE* fRegistros) {
    fseek(fRegistros, offset, SEEK_SET);
    char buffer[REG_TAM_MAX];
    int tamReg;
    fread(&tamReg, sizeof (tamReg), 1, fRegistros);
    fread(buffer, sizeof (char), tamReg, fRegistros);
    int j = getCampo(buffer, registro->inscricao);
    j += getCampo(buffer + j, registro->nome);
    j += getCampo(buffer + j, registro->curso);
    j += getCampo(buffer + j, registro->nota);
}

int buscaIndicePrimario(char* chave, struct PrimaryIndex* index) {
    sprintf(chave, "%*d", TAM_CHAVE_PRI, atoi(chave));
    int achou;
    int pos;
    buscaBinaria(index->vetor, sizeof (index->vetor[0]), index->tamanho, chave, &achou, &pos);
    if (achou)
        return pos;

    return -1;
}

int buscaIndiceSecundario(char* chave, struct SecundaryIndex* indice) {
    int achou;
    int pos;
    buscaBinaria(indice->vetor, sizeof (indice->vetor[0]), indice->tamanho, chave, &achou, &pos);
    if (achou)
        return pos;

    return -1;
}

void buscaBinaria(void* vetor, size_t tamanhoElemento, size_t tamanho, char* chave, int* achou, int* pos) {
    int i = 0;
    int j = tamanho - 1;
    *pos = (i + j) / 2;
    int valor;
    while (i <= j && (valor = comparaChaves(chave, vetor + *pos * tamanhoElemento))) {
        if (valor > 0)
            i = *pos + 1;
        else
            j = *pos - 1;
        *pos = (i + j) / 2;
    }
    *achou = i <= j;
}